/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// cl_parse.cpp - server message parsing
//


#include "client.h"


static const char *		cl_svcOps[32] = {
	"SVC_BAD",
	"SVC_NOP",
	"SVC_COMMAND",
	"SVC_SERVERSTATE",
	"SVC_GAMESTATE",
	"SVC_SNAPSHOT",
	"SVC_VOICE",
	"SVC_DOWNLOAD",
	"SVC_EOF"
};


/*
 ==============================================================================

 SERVER COMMAND EXECUTION

 ==============================================================================
*/


/*
 ==================
 CL_DisconnectCommand
 ==================
*/
static void CL_DisconnectCommand (){

	if (Cmd_Argc() == 1)
		Com_Error(false, "Server disconnected for unknown reason");
	else
		Com_Error(false, "Server disconnected - %s", Cmd_Args(1, -1, false));
}

/*
 ==================
 CL_PrintCommand
 ==================
*/
static void CL_PrintCommand (){

	Com_Printf("%s\n", Cmd_Args(1, -1, false));
}

/*
 ==================
 
 ==================
*/
static void CL_ExecuteServerCommand (const char *command){

	const char	*cmd;

	Com_DPrintf("Server command: %s\n", command);

	// Execute the server command
	Cmd_TokenizeString(command);

	if (!Cmd_Argc())
		return;			// No tokens

	cmd = Cmd_Argv(0);

	if (!Str_ICompare(cmd, "disconnect")){
		CL_DisconnectCommand();
		return;
	}

	if (!Str_ICompare(cmd, "print")){
		CL_PrintCommand();
		return;
	}

	// Pass unknown server commands to the game module if possible
	if (glqCGame){
		glqCGame->ServerCommand();
		return;
	}

	Com_DPrintf("Server command ignored: %s\n", cmd);
}


/*
 ==============================================================================

 SERVER MESSAGE PARSING

 ==============================================================================
*/


/*
 ==================
 CL_ParseCommand
 ==================
*/
static void CL_ParseCommand (msg_t *msg){

	char	command[MAX_STRING_LENGTH];
	int		sequence;

	// Parse the command
	sequence = MSG_ReadLong(msg);
	MSG_ReadString(msg, command, sizeof(command));

	// See if we have already executed it
	if (clc.serverCommandSequence >= sequence)
		return;

	// Drop the connection if we have somehow lost commands
	if (sequence > clc.serverCommandSequence + 1)
		Com_Error(false, "CL_ParseCommand: lost %i reliable commands", sequence - clc.serverCommandSequence + 1);

	clc.serverCommandSequence = sequence;

	// Execute it
	CL_ExecuteServerCommand(command);
}

/*
 ==================
 
 ==================
*/
static void CL_ParseServerState (msg_t *msg){

	if (cls.state != CS_CONNECTED || glqCGame)
		Com_Error(false, "CL_ParseServerState: unexpected server state received");

	// Wipe the entire clientActive_t structure
	Mem_Fill(&cl, 0, sizeof(clientActive_t));

	// Parse the server command sequence
	clc.serverCommandSequence = MSG_ReadLong(msg);

	// Parse the client number
	cl.clientNum = MSG_ReadByte(msg);

	// Parse the server id
	cl.serverId = MSG_ReadLong(msg);

	// TODO!!!

	// Parse the pure mode
	cl.serverPure = MSG_ReadBool(msg);

	// TODO!!!

	// Enter the download state
	CL_RequestDownloads();
}

/*
 ==================
 CL_ParseGameState
 ==================
*/
static void CL_ParseGameState (msg_t *msg){

	int		offset;

	if (cls.state != CS_CONNECTED || !glqCGame)
		Com_Error(false, "CL_ParseGameState: unexpected game state received");

	// Parse the game state
	offset = glqCGame->ParseGameState(msg);

	// Request any remaining state and return immediately if needed
	if (offset >= 0){
		CL_AddReliableCommand("gameState %i", offset);
		return;
	}

	// Load the level
	CG_LoadLevel();
}

/*
 ==================
 CL_ParseSnapshot
 ==================
*/
static void CL_ParseSnapshot (msg_t *msg){

	int		time;
	bool	deltaCompressed;

	if (cls.state < CS_PRIMED)
		Com_Error(false, "CL_ParseSnapshot: unexpected snapshot received");

	// Parse the snapshot
	if (!glqCGame->ParseSnapshot(msg, clc.serverMessageSequence, &time, &deltaCompressed))
		return;

	// We have successfully received a valid snapshot
	cl.snapshotAvailable = true;

	cl.snapshotValid = true;
	cl.snapshotTime = time;
	cl.snapshotSequence = clc.serverMessageSequence;

	// If it was an uncompressed snapshot, we can start recording now
	if (!deltaCompressed)
		clc.demoWaiting = false;
}

/*
 ==================
 
 ==================
*/
static void CL_ParseVoice (msg_t *msg){

}

/*
 ==================
 
 ==================
*/
static void CL_ParseDownload (msg_t *msg){

}

/*
 ==================
 CL_ParseServerMessage
 ==================
*/
void CL_ParseServerMessage (msg_t *msg){

	int		op;

	MSG_ClearReadState(msg);

	// Parse the message sequence
	clc.serverMessageSequence = MSG_ReadLong(msg);

	// Parse the reliable acknowledge
	clc.reliableCommandAcknowledge = MSG_ReadLong(msg);

	if (clc.reliableCommandAcknowledge < clc.reliableCommandSequence - MAX_RELIABLE_COMMANDS)
		clc.reliableCommandAcknowledge = clc.reliableCommandSequence;

	// Parse the message
	while (1){
		if (msg->readCount > msg->writeCount)
			Com_Error(false, "CL_ParseServerMessage: read past end of server message");

		// Parse the server op
		op = MSG_ReadByte(msg);

		if (cl_showMessages->integerValue){
			if (!cl_svcOps[op])
				Com_Printf("%4i: BAD OP %i\n", msg->readBytes - 1, op);
			else
				Com_Printf("%4i: %s\n", msg->readBytes - 1, cl_svcOps[op]);
		}

		if (op == SVC_EOF){
			if (cl_showMessages->integerValue){
				Com_Printf("--------------------\n");
				Com_Printf("%i bytes message\n", msg->writeBytes);
				Com_Printf("\n");
			}

			break;
		}

		switch (op){
		case SVC_NOP:

			break;
		case SVC_COMMAND:
			CL_ParseCommand(msg);
			break;
		case SVC_SERVERSTATE:
			CL_ParseServerState(msg);
			break;
		case SVC_GAMESTATE:
			CL_ParseGameState(msg);
			break;
		case SVC_SNAPSHOT:
			CL_ParseSnapshot(msg);
			break;
		case SVC_VOICE:
			CL_ParseVoice(msg);
			break;
		case SVC_DOWNLOAD:
			CL_ParseDownload(msg);
			break;
		default:
			Com_Error(false, "CL_ParseServerMessage: illegible server message");
		}
	}
}